/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details. *
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/workqueue.h>

#define CFG_HPD_INTERRUPTS	    BIT(0)
#define CFG_EDID_INTERRUPTS	    BIT(1)
#define CFG_CEC_INTERRUPTS	    BIT(2)
#define CFG_VID_CHK_INTERRUPTS	BIT(3)

#define EDID_SEG_SIZE	        0x100

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
#endif

typedef enum {
	MIPI_1LANE = 1,
	MIPI_2LANE = 2,
	MIPI_3LANE = 3,
	MIPI_4LANE = 0,
} mipi_lane_counts;


typedef enum {
	MIPI_1PORT = 0x00,
	MIPI_2PORT = 0x03,
} mipi_port_counts;

typedef enum {
	VIDEO_640x480_60HZ = 0,
	VIDEO_720x480_60HZ = 1,
	VIDEO_1280x720_60HZ = 2,
	VIDEO_1920x1080_30HZ = 3,
	VIDEO_1920x1080_60HZ = 4,
	VIDEO_3840x1080_60HZ = 5,
	VIDEO_3840x2160_30HZ = 6,
} video_format_id;

typedef struct {
    u8 reg;
    u8 val;
} lt9611_reg_cfg;


struct lt9611_video_cfg {
	u32 h_front_porch;
	u32 h_pulse_width;
	u32 h_back_porch;
	u32 h_active;
	u32 v_front_porch;
	u32 v_pulse_width;
	u32 v_back_porch;
	u32 v_active;
	bool h_polarity;
	bool v_polarity;
	u32 vic;
	u32 pclk_khz;
	u8 *video_mode;
};

struct lt9611 {
	struct i2c_client *i2c_client;

	mipi_port_counts mipi_port_counts;
	mipi_lane_counts mipi_lane_counts;
	video_format_id	video_format_id;

	int irq;
	int irq_gpio;
	int reset_gpio;

	u8 pcr_m;
    u8 edid_buf[EDID_SEG_SIZE];

	struct delayed_work on_work;
};

static struct lt9611 *this_lt9611 = NULL;


static struct lt9611_video_cfg video_tab[] = {
	{ 8,   96,  40,	640,  33, 2,  10, 480,	0, 0, 0,  25000	,  "video_640x480_60Hz"},
	{ 16,  62,  60,	720,  9,  6,  30, 480,	0, 0, 0,  27000	,  "video_720x480_60Hz"},
	{ 110, 40, 220, 1280, 5,  5,  20, 720,	1, 1, 0,  74250	,  "video_1280x720_60Hz"},
	{ 88,  44, 148, 1920, 4,  5,  36, 1080, 1, 1, 0,  74250	,  "video_1920x1080_30Hz"},
	{ 88,  44, 148, 1920, 4,  5,  36, 1080, 1, 1, 16, 148500 , "video_1920x1080_60Hz"},
	{ 176, 88, 296, 3840, 4,  5,  36, 1080, 1, 1, 0,  297000 , "video_3840x1080_60Hz"},
	{ 176, 88, 296, 3840, 8,  10, 72, 2160, 1, 1, 0,  297000 , "video_3840x2160_30Hz"},
};

/* Start [The function declares the region in advance] */

static int lt9611_probe(struct i2c_client *client, const struct i2c_device_id *id);
static int lt9611_remove(struct i2c_client *client);
static int lt9611_gpio_deinit(struct lt9611 *pdata);
static int lt9611_config(struct lt9611 *pdata);

/* End [The function declares the region in advance] */

static int i2c_write_byte(struct i2c_client *client, u8 addr, u8 reg, u8 val)
{
	int rc = 0;
	struct i2c_msg msg;
	u8 buf[2] = { reg, val };

	if (!client) {
		printk("%s: Invalid params\n", __func__);
		return -EINVAL;
	}

	msg.addr = addr;
	msg.flags = 0;
	msg.len = 2;
	msg.buf = buf;

	if (i2c_transfer(client->adapter, &msg, 1) < 1) {
		printk("%s: i2c write failed\n", __func__);
		rc = -EIO;
	}

	return rc;
}

static int i2c_read(struct i2c_client *client, u8 addr, u8 reg, char *buf, u32 size)
{
	int rc = 0;
	struct i2c_msg msg[2];

	if (!client || !buf) {
		printk("%s: Invalid params\n", __func__);
		return -EINVAL;
	}

	msg[0].addr = addr;
	msg[0].flags = 0;
	msg[0].len = 1;
	msg[0].buf = &reg;

	msg[1].addr = addr;
	msg[1].flags = I2C_M_RD;
	msg[1].len = size;
	msg[1].buf = buf;

	if (i2c_transfer(client->adapter, msg, 2) != 2) {
		printk("%s: i2c read failed\n", __func__);
		rc = -EIO;
	}

	return rc;
}



static int lt9611_read_reg(struct lt9611 *pdata, u8 reg, char *buf, u32 size)
{
	int ret = 0;

	ret = i2c_read(pdata->i2c_client, pdata->i2c_client->addr, reg, buf, size);
	if (ret) {
		printk("%s: read err: reg 0x%02x, size 0x%02x\n", __func__, reg, size);
	}

	return ret;
}

static int lt9611_write_reg(struct lt9611 *pdata, u8 reg, u8 val)
{
	int ret = 0;

	ret = i2c_write_byte(pdata->i2c_client, pdata->i2c_client->addr, reg, val);
	if (ret) {
		printk("%s: wr err: reg 0x%02x, val 0x%02x\n", __func__, reg, val);
	}

	return ret;
}

static int lt9611_write_multi_reg(struct lt9611 *pdata, const lt9611_reg_cfg *list, u32 reg_num)
{
    int ret = 0;
    int i;

    for (i = 0; i < reg_num; i++) {

        ret = i2c_write_byte(pdata->i2c_client, pdata->i2c_client->addr, list[i].reg, list[i].val);
        if (ret){
            printk("%s: wr err: i=%d reg 0x%02x, val 0x%02x\n", __func__, i, list[i].reg, list[i].val);
            return ret;
        }
    }

    return ret;
}

static int lt9611_read_device_rev(struct lt9611 *pdata)
{
	int ret = 0;

	ret |= lt9611_write_reg(pdata, 0xff, 0x80);
	ret |= lt9611_write_reg(pdata, 0xff, 0x80);
	ret |= lt9611_write_reg(pdata, 0xff, 0x80);

	ret |= lt9611_write_reg(pdata, 0xee, 0x01);

	return ret;
}



static int lt9611_pcr_mk_debug(struct lt9611 *pdata)
{
	u8 m, k1, k2, k3;
	u8 reg_8397h;
	u8 i;
    int ret = 0;

	for (i = 0; i < 30; i++) {
		ret |= lt9611_write_reg(pdata, 0xff, 0x83);
		ret |= lt9611_read_reg(pdata, 0x97, &reg_8397h, 1);
		ret |= lt9611_read_reg(pdata, 0xb4, &m, 1);
		ret |= lt9611_read_reg(pdata, 0xb5, &k1, 1);
		ret |= lt9611_read_reg(pdata, 0xb6, &k2, 1);
		ret |= lt9611_read_reg(pdata, 0xb7, &k3, 1);

		printk("%s: 0x8397 = 0x%x; pcr mk:0x%x 0x%x 0x%x 0x%x\n", __func__,
				reg_8397h, m, k1, k2, k3);
	}

	return ret;
}

static int lt9611_htotal_stable_debug(struct lt9611 *pdata)
{
	u32 h_total_sysclk;
	u8 val, i;
	int ret = 0;

	ret |= lt9611_write_reg(pdata, 0xff, 0x82);

	for (i = 0; i < 30; i++) {
		/* h_total_sysclk */
		ret |= lt9611_read_reg(pdata, 0x86, &val, 1);
		if (ret)
			goto end;
		h_total_sysclk = val << 8;
		ret |= lt9611_read_reg(pdata, 0x87, &val, 1);
		if (ret)
			goto end;
		h_total_sysclk = h_total_sysclk + val;

		printk("%s: h_total_sysclk = %d\n", __func__, h_total_sysclk);
	}

	return 0;
end:
	printk(KERN_ERR "%s: read htotal by system clock error\n", __func__);
	return ret;
}

static int lt9611_mipi_byte_clk_debug(struct lt9611 *pdata)
{
	u8 reg_val;
	u32 byte_clk;
    int ret = 0;

	/* port A byte clk meter */
	ret |= lt9611_write_reg(pdata, 0xff, 0x82);
	ret |= lt9611_write_reg(pdata, 0xc7, 0x03);        /* port A */
	msleep(50);
	ret |= lt9611_read_reg(pdata, 0xcd, &reg_val, 1);

	if ((reg_val & 0x60) == 0x60) {
		byte_clk = (reg_val & 0x0f) * 65536;
		ret |= lt9611_read_reg(pdata, 0xce, &reg_val, 1);
		byte_clk = byte_clk + reg_val * 256;
		ret |= lt9611_read_reg(pdata, 0xcf, &reg_val, 1);
		byte_clk = byte_clk + reg_val;

		printk("%s: port A byte clk = %d khz,\n", __func__, byte_clk);
	} 
    else {
		printk("%s: port A byte clk unstable\n", __func__);
	}

	/* port B byte clk meter */
	ret |= lt9611_write_reg(pdata, 0xff, 0x82);
	ret |= lt9611_write_reg(pdata, 0xc7, 0x04); /* port B */
	msleep(50);
	ret |= lt9611_read_reg(pdata, 0xcd, &reg_val, 1);

	if ((reg_val & 0x60) == 0x60) {
		byte_clk = (reg_val & 0x0f) * 65536;
		ret |= lt9611_read_reg(pdata, 0xce, &reg_val, 1);
		byte_clk = byte_clk + reg_val * 256;
		ret |= lt9611_read_reg(pdata, 0xcf, &reg_val, 1);
		byte_clk = byte_clk + reg_val;

		printk("%s: port B byte clk = %d khz,\n", __func__, byte_clk);
	} 
    else {
		printk("%s: port B byte clk unstable\n", __func__);
	}

	return 0;
}


static int lt9611_reset(struct lt9611 *pdata)
{
	gpio_direction_output(pdata->reset_gpio, 1);
	msleep(100);
	gpio_direction_output(pdata->reset_gpio, 0);
	msleep(100);
	gpio_direction_output(pdata->reset_gpio, 1);

    return 0;
}

static int lt9611_parse_dt(struct device *dev, struct lt9611 *pdata)
{
    struct device_node *np = dev->of_node;
    int ret = 0;
    u32 video_format_id = 0;
    u32 mipi_lane_counts = 0;
    u32 mipi_port_counts = 0;

    pdata->irq_gpio = of_get_named_gpio(np, "lt,irq-gpio", 0);
    if (!gpio_is_valid(pdata->irq_gpio)) {
        printk(KERN_ERR "%s: irq gpio not specified\n", __func__);
        return -EINVAL;
    }

    pdata->reset_gpio = of_get_named_gpio(np, "lt,reset-gpio", 0);
    if (!gpio_is_valid(pdata->reset_gpio)) {
        printk(KERN_ERR "%s: reset gpio not specified\n", __func__);
        return -EINVAL;
    }

    /* video_format_id */
    ret = of_property_read_u32(np, "lt,video-format-id", &video_format_id);
    if (ret < 0) {
        printk(KERN_ERR "%s: Failed to read lt,video-format-id\n", __func__);
        return ret;
    }

    if (video_format_id >= 0 && video_format_id < ARRAY_SIZE(video_tab)) {
        printk(KERN_INFO "%s: Device Tree Set Video Resolution: %s\n", __func__,
               video_tab[video_format_id].video_mode);
        pdata->video_format_id = video_format_id;
    } else {
        printk(KERN_ERR "%s: Invalid Video Resolution ID\n", __func__);
        return -EINVAL;
    }

    /* mipi_lane_counts */
    ret = of_property_read_u32(np, "lt,mipi-lanes-type", &mipi_lane_counts);
    if (ret < 0) {
        printk(KERN_ERR "%s: Failed to read lt,mipi-lanes-type\n", __func__);
        return ret;
    }

    if (mipi_lane_counts == MIPI_1LANE || mipi_lane_counts == MIPI_2LANE ||
        mipi_lane_counts == MIPI_3LANE || mipi_lane_counts == MIPI_4LANE) {
        pdata->mipi_lane_counts = mipi_lane_counts;
        printk(KERN_INFO "%s: Device Tree Set MIPI LANE: %d\n", __func__, mipi_lane_counts);
    } else {
        printk(KERN_ERR "%s: Invalid MIPI LANE specified\n", __func__);
        return -EINVAL;
    }

    /* mipi_port_counts */
    ret = of_property_read_u32(np, "lt,mipi-ports-type", &mipi_port_counts);
    if (ret < 0) {
        printk(KERN_ERR "%s: Failed to read lt,mipi-ports-type\n", __func__);
        return ret;
    }

    if (mipi_port_counts == MIPI_1PORT || mipi_port_counts == MIPI_2PORT) {
        pdata->mipi_port_counts = mipi_port_counts;
        printk(KERN_INFO "%s: Device Tree Set MIPI PORT: %d\n", __func__, mipi_port_counts);
    } else {
        printk(KERN_ERR "%s: Invalid MIPI PORT specified\n", __func__);
        return -EINVAL;
    }

    return ret;
}



static int lt9611_gpio_init(struct lt9611 *pdata)
{
	int ret = 0;

	ret = gpio_request(pdata->reset_gpio, "lt9611-reset-gpio");
	if (!ret) {
		printk(KERN_INFO "%s: reset gpio request success!\n", __func__);
		gpio_direction_output(pdata->reset_gpio, 1);
		msleep(10);
	}

	ret = gpio_request(pdata->irq_gpio, "lt9611-irq-gpio");
	if (!ret) {
		printk(KERN_INFO "%s: irq gpio request success!\n", __func__);
		gpio_direction_input(pdata->irq_gpio);
	}

	return ret;
}


/*

=======================================================================================

*/

static int lt9611_init_system(struct lt9611 *pdata)
{
    const lt9611_reg_cfg system_regs[] = {
        { 0xff, 0x81 },
        { 0x01, 0x18 }, /* sel xtal clock */
        { 0xff, 0x82 },
        { 0x51, 0x11 },

        /* timer for frequency meter */
        { 0xff, 0x82 },
        { 0x1b, 0x69 }, /* timer 2 */
        { 0x1c, 0x78 },
        { 0xcb, 0x69 }, /* timer 1 */
        { 0xcc, 0x78 },

        /* power consumption for work */
        { 0xff, 0x80 },
        { 0x04, 0xf0 },
        { 0x06, 0xf0 },
        { 0x0a, 0x80 },
        { 0x0b, 0x46 },
        { 0x0d, 0xef },
        { 0x11, 0xfa },
    };

    return lt9611_write_multi_reg(pdata, system_regs, ARRAY_SIZE(system_regs));
}

static int lt9611_mipi_input_analog_K(struct lt9611 *pdata)
{
    const lt9611_reg_cfg mipi_input_analog_regs[] = {
        { 0xff, 0x81 },
        { 0x06, 0x60 }, /* port A rx current */
        { 0x07, 0x3f }, /* port A lane0 and lane1 eq set */
        { 0x08, 0x3f }, /* port A lane2 and lane3 eq set */
        { 0x0a, 0xfe }, /* port A ldo voltage set */
        { 0x0b, 0xbf }, /* enable port A lprx */

        { 0x11, 0x60 }, /* port B rx current */
        { 0x12, 0x3f }, /* port B lane0 and lane1 eq set */
        { 0x13, 0x3f }, /* port B lane2 and lane3 eq set */
        { 0x15, 0xfe }, /* port B ldo voltage set */
        { 0x16, 0xbf }, /* enable port B lprx */

        { 0x1c, 0x03 }, /* PortA clk lane no-LP mode */
        { 0x20, 0x03 }, /* PortB clk lane no-LP mode */
    };

    return lt9611_write_multi_reg(pdata, mipi_input_analog_regs, ARRAY_SIZE(mipi_input_analog_regs));
}

static int lt9611_mipi_input_digital_K(struct lt9611 *pdata)
{
    int ret = 0;
	
    #if 0    /* LT9611_PORTA */

        ret |= lt9611_write_reg(pdata, 0xff, 0x82);
        ret |= lt9611_write_reg(pdata, 0x50, 0x10);
        ret |= lt9611_write_reg(pdata, 0xff, 0x83);
        ret |= lt9611_write_reg(pdata, 0x03, 0x00);

    #else    /* LT9611_PORTB */

        ret |= lt9611_write_reg(pdata, 0xff, 0x82);
        ret |= lt9611_write_reg(pdata, 0x50, 0x14);

        ret |= lt9611_write_reg(pdata, 0xff, 0x83);
        ret |= lt9611_write_reg(pdata, 0x00, 0x60);
        ret |= lt9611_write_reg(pdata, 0x03, 0x4f);
        ret |= lt9611_write_reg(pdata, 0x04, 0x00);
        ret |= lt9611_write_reg(pdata, 0x07, 0x40);

    #endif

    ret |= lt9611_write_reg(pdata, 0xff, 0x82);
    ret |= lt9611_write_reg(pdata, 0x4f, 0x80);
    ret |= lt9611_write_reg(pdata, 0xff, 0x83);
    ret |= lt9611_write_reg(pdata, 0x02, 0x08);
    ret |= lt9611_write_reg(pdata, 0x06, 0x08);
    ret |= lt9611_write_reg(pdata, 0x0a, 0x00);

    return ret;
}


static int lt9611_setup_pll(struct lt9611 *pdata)
{
    int postdiv = 0;
    int pclk = 0;
    int video_format_id = 0;
    int ret = 0;

    const lt9611_reg_cfg pll_regs[] = {
        { 0xff, 0x81 },
        { 0x23, 0x40 },
        { 0x24, 0x62 },
        { 0x25, 0x80 },
        { 0x26, 0x55 },
        { 0x2c, 0x37 },
        { 0x2f, 0x01 },
        { 0x27, 0x66 },
        { 0x28, 0x88 },
        { 0x2a, 0x20 },
    };

	struct lt9611_video_cfg *cfg;

    video_format_id = pdata->video_format_id;
	cfg = &video_tab[video_format_id];
	pclk = cfg->pclk_khz;

    printk(KERN_INFO "%s: video_format_id=%d  cfg->pclk_khz = %d\r\n", __func__, video_format_id, pclk);

    ret |= lt9611_write_multi_reg(pdata, pll_regs, ARRAY_SIZE(pll_regs));

    if (pclk > 150000) {
        ret |= lt9611_write_reg(pdata, 0x2d, 0x88);
        postdiv = 1;
    } 
    else if (pclk > 80000) {
        ret |= lt9611_write_reg(pdata, 0x2d, 0x99);
        postdiv = 2;
    } 
    else {
        ret |= lt9611_write_reg(pdata, 0x2d, 0xaa);
        postdiv = 4;
    }
    pdata->pcr_m = ((pclk * 5 * postdiv) / 27000) - 1;
 
    ret |= lt9611_write_reg(pdata, 0xff, 0x83);
	ret |= lt9611_write_reg(pdata, 0x2d, 0x40);
	ret |= lt9611_write_reg(pdata, 0x31, 0x08);
    ret |= lt9611_write_reg(pdata, 0x26, 0x80 | pdata->pcr_m);

    ret |= lt9611_write_reg(pdata, 0xff, 0x82);
    pclk = pclk / 2;
    ret |= lt9611_write_reg(pdata, 0xe3, pclk / 65536);
    pclk = pclk % 65536;
    ret |= lt9611_write_reg(pdata, 0xe4, pclk / 256);
    ret |= lt9611_write_reg(pdata, 0xe5, pclk % 256);

    ret |= lt9611_write_reg(pdata, 0xff, 0x82);
    ret |= lt9611_write_reg(pdata, 0xde, 0x20);
    ret |= lt9611_write_reg(pdata, 0xde, 0xe0);

    ret |= lt9611_write_reg(pdata, 0xff, 0x80);
    ret |= lt9611_write_reg(pdata, 0x11, 0x5a);
    ret |= lt9611_write_reg(pdata, 0x11, 0xfa);
    ret |= lt9611_write_reg(pdata, 0x16, 0xf2);
    ret |= lt9611_write_reg(pdata, 0x18, 0xdc);
    ret |= lt9611_write_reg(pdata, 0x18, 0xfc);
    ret |= lt9611_write_reg(pdata, 0x16, 0xf3);

    return ret;
}

static int lt9611_setup_pcr(struct lt9611 *pdata)
{
    int ret = 0;

    const lt9611_reg_cfg pcr_regs[] = {
        { 0xff, 0x83 },
        { 0x0b, 0x01 },
        { 0x0c, 0x10 },
        { 0x48, 0x00 },
        { 0x49, 0x81 },

        /* stage 1 */
        { 0x21, 0x4a },
        { 0x24, 0x71 },
        { 0x25, 0x30 },
        { 0x2a, 0x01 },

        /* stage 2 */
        { 0x4a, 0x40 },

		/* MK limit */
		{ 0x2d, 0x40 },
		{ 0x31, 0x08 },
    };

    ret |= lt9611_write_multi_reg(pdata, pcr_regs, ARRAY_SIZE(pcr_regs));

    ret |= lt9611_write_reg(pdata, 0xff, 0x83);
    ret |= lt9611_write_reg(pdata, 0x1d, 0x10);
    ret |= lt9611_write_reg(pdata, 0x26, pdata->pcr_m);

    ret |= lt9611_write_reg(pdata, 0xff, 0x80);
    ret |= lt9611_write_reg(pdata, 0x11, 0x5a);
    ret |= lt9611_write_reg(pdata, 0x11, 0xfa);

    return ret;
}


static int lt9611_setup_timing(struct lt9611 *pdata)
{
	u32 h_total, h_act, hpw, hfp, hss;
	u32 v_total, v_act, vpw, vfp, vss;
	video_format_id video_id;
    int ret = 0;
	struct lt9611_video_cfg *cfg;

	video_id = pdata->video_format_id;
	cfg = &video_tab[video_id];

	h_total = cfg->h_active + cfg->h_front_porch + cfg->h_pulse_width + cfg->h_back_porch;
	v_total = cfg->v_active + cfg->v_front_porch + cfg->v_pulse_width + cfg->v_back_porch;

	h_act = cfg->h_active;
	hpw = cfg->h_pulse_width;
	hfp = cfg->h_front_porch;
	hss = cfg->h_pulse_width + cfg->h_back_porch;

	v_act = cfg->v_active;
	vpw = cfg->v_pulse_width;
	vfp = cfg->v_front_porch;
	vss = cfg->v_pulse_width + cfg->v_back_porch;

	printk(KERN_INFO "%s: h_total=%d, h_active=%d, hfp=%d, hpw=%d, hbp=%d\n", __func__,
			h_total, cfg->h_active, cfg->h_front_porch,
			cfg->h_pulse_width, cfg->h_back_porch);

	printk(KERN_INFO "%s: v_total=%d, v_active=%d, vfp=%d, vpw=%d, vbp=%d\n", __func__,
			v_total, cfg->v_active, cfg->v_front_porch,
			cfg->v_pulse_width, cfg->v_back_porch);

	ret |= lt9611_write_reg(pdata, 0xff, 0x83);

	ret |= lt9611_write_reg(pdata, 0x0d, (u8)(v_total / 256));
	ret |= lt9611_write_reg(pdata, 0x0e, (u8)(v_total % 256));

	ret |= lt9611_write_reg(pdata, 0x0f, (u8)(v_act / 256));
	ret |= lt9611_write_reg(pdata, 0x10, (u8)(v_act % 256));

	ret |= lt9611_write_reg(pdata, 0x11, (u8)(h_total / 256));
	ret |= lt9611_write_reg(pdata, 0x12, (u8)(h_total % 256));

	ret |= lt9611_write_reg(pdata, 0x13, (u8)(h_act / 256));
	ret |= lt9611_write_reg(pdata, 0x14, (u8)(h_act % 256));

	ret |= lt9611_write_reg(pdata, 0x15, (u8)(vpw % 256));
	ret |= lt9611_write_reg(pdata, 0x16, (u8)(hpw % 256));

	ret |= lt9611_write_reg(pdata, 0x17, (u8)(vfp % 256));

	ret |= lt9611_write_reg(pdata, 0x18, (u8)(vss % 256));

	ret |= lt9611_write_reg(pdata, 0x19, (u8)(hfp % 256));

	ret |= lt9611_write_reg(pdata, 0x1a, (u8)(hss / 256));
	ret |= lt9611_write_reg(pdata, 0x1b, (u8)(hss % 256));

	return ret;
}

static int lt9611_hdmi_tx_digital_K(struct lt9611 *pdata)
{
    int ret = 0;

    ret |= lt9611_write_reg(pdata, 0xff, 0x84);
    ret |= lt9611_write_reg(pdata, 0x43, 0x21);
    ret |= lt9611_write_reg(pdata, 0x45, 0x40);
    ret |= lt9611_write_reg(pdata, 0x47, 0x34);
    ret |= lt9611_write_reg(pdata, 0x3d, 0x0a); /* UD1 infoframe */

    ret |= lt9611_write_reg(pdata, 0xff, 0x82);
    ret |= lt9611_write_reg(pdata, 0xd6, 0x8e);
    ret |= lt9611_write_reg(pdata, 0xd7, 0x04);

    return ret;
}

static int lt9611_hdmi_tx_phy_K(struct lt9611 *pdata)
{
    const lt9611_reg_cfg hdmi_tx_phy_regs[] = {
        { 0xff, 0x81 },
        { 0x30, 0x6a },
        { 0x31, 0x44 }, /* HDMI DC mode */
        { 0x32, 0x4a },
        { 0x33, 0x0b },
        { 0x34, 0x00 },
        { 0x35, 0x00 },
        { 0x36, 0x00 },
        { 0x37, 0x44 },
        { 0x3f, 0x0f },
        { 0x40, 0x98 },
        { 0x41, 0x98 },
        { 0x42, 0x98 },
        { 0x43, 0x98 },
        { 0x44, 0x0a },
    };

    return lt9611_write_multi_reg(pdata, hdmi_tx_phy_regs, ARRAY_SIZE(hdmi_tx_phy_regs));
}



static int lt9611_enable_hpd_interrupts(struct lt9611 *pdata)
{
    int ret = 0;

    ret |= lt9611_write_reg(pdata, 0xff, 0x82);
    ret |= lt9611_write_reg(pdata, 0x58, 0x0a);
    ret |= lt9611_write_reg(pdata, 0x59, 0x80);
    ret |= lt9611_write_reg(pdata, 0x9e, 0xf7);

    ret |= lt9611_write_reg(pdata, 0x00, 0xff);
    ret |= lt9611_write_reg(pdata, 0x03, 0x3f);

    ret |= lt9611_write_reg(pdata, 0x04, 0xff);
    ret |= lt9611_write_reg(pdata, 0x04, 0xfe);

    ret |= lt9611_write_reg(pdata, 0x07, 0xff);
    ret |= lt9611_write_reg(pdata, 0x07, 0x3f);

    return ret;
}

static int lt9611_enable_hdmi_out(struct lt9611 *pdata)
{
    lt9611_write_reg(pdata, 0xff, 0x81);
    lt9611_write_reg(pdata, 0x23, 0x40);

    lt9611_write_reg(pdata, 0xff, 0x82);
    lt9611_write_reg(pdata, 0xde, 0x20);
    lt9611_write_reg(pdata, 0xde, 0xe0);

    lt9611_write_reg(pdata, 0xff, 0x80);
    lt9611_write_reg(pdata, 0x18, 0xdc);
    lt9611_write_reg(pdata, 0x18, 0xfc);
    lt9611_write_reg(pdata, 0x16, 0xf1);
    lt9611_write_reg(pdata, 0x16, 0xf3);
    lt9611_write_reg(pdata, 0x11, 0x5a);
    lt9611_write_reg(pdata, 0x11, 0xfa);

    lt9611_write_reg(pdata, 0xff, 0x81);
    lt9611_write_reg(pdata, 0x30, 0xea);

    return 0;
}

static int lt9611_get_chip_id_debug(struct lt9611 *pdata)
{
    int ret = 0;
    int chip_id = 0;
    u8 id_high;
    u8 id_mid;

    ret |= lt9611_read_reg(pdata, 0x00, &id_high, 1);
    ret |= lt9611_read_reg(pdata, 0x01, &id_mid, 1);

    chip_id = (id_high << 8) | id_mid;

	printk(KERN_INFO "%s: lt9611 chip_id %02x\n", __func__, chip_id);

    return ret;
}


static int lt9611_read_video_debug(struct lt9611 *pdata)
{
	u32 v_total, v_act, h_act_a, h_act_b, h_total_sysclk;
	u8 val;
	int ret = 0;

	/* top module video check */
	lt9611_write_reg(pdata, 0xff, 0x82);

	/* v_act */
	ret = lt9611_read_reg(pdata, 0x82, &val, 1);
	if (ret)
		goto end;

	v_act = val << 8;
	ret = lt9611_read_reg(pdata, 0x83, &val, 1);
	if (ret)
		goto end;
	v_act = v_act + val;

	/* v_total */
	ret = lt9611_read_reg(pdata, 0x6c, &val, 1);
	if (ret)
		goto end;
	v_total = val << 8;
	ret = lt9611_read_reg(pdata, 0x6d, &val, 1);
	if (ret)
		goto end;
	v_total = v_total + val;

	/* h_total_sysclk */
	ret = lt9611_read_reg(pdata, 0x86, &val, 1);
	if (ret)
		goto end;
	h_total_sysclk = val << 8;
	ret = lt9611_read_reg(pdata, 0x87, &val, 1);
	if (ret)
		goto end;
	h_total_sysclk = h_total_sysclk + val;

	/* h_act_a */
	lt9611_write_reg(pdata, 0xff, 0x83);
	ret = lt9611_read_reg(pdata, 0x82, &val, 1);
	if (ret)
		goto end;
	h_act_a = val << 8;
	ret = lt9611_read_reg(pdata, 0x83, &val, 1);
	if (ret)
		goto end;
	h_act_a = (h_act_a + val) / 3;

	/* h_act_b */
	lt9611_write_reg(pdata, 0xff, 0x83);
	ret = lt9611_read_reg(pdata, 0x86, &val, 1);
	if (ret)
		goto end;
	h_act_b = val << 8;
	ret = lt9611_read_reg(pdata, 0x87, &val, 1);
	if (ret)
		goto end;
	h_act_b = (h_act_b + val) / 3;

	printk(KERN_INFO "%s: video check: h_act_a=%d, h_act_b=%d, v_act=%d, v_total=%d, h_total_sysclk=%d\n",
			__func__, h_act_a, h_act_b, v_act, v_total, h_total_sysclk);

	return 0;

end:
	printk(KERN_ERR "%s: read video check error\n", __func__);
	return ret;

}


static void lt9611_on_work_fn (struct work_struct *work)
{
    printk("\n ================= [ Read Debug Start ] ================ \n");

    lt9611_read_video_debug(this_lt9611);
    lt9611_get_chip_id_debug(this_lt9611);
    lt9611_mipi_byte_clk_debug(this_lt9611);
    lt9611_pcr_mk_debug(this_lt9611);
    lt9611_htotal_stable_debug(this_lt9611);

    printk("\n ================= [ Read Debug End ] ================== \n");
}

static int lt9611_read_edid(struct lt9611 *pdata)
{
	int ret = 0;
	u8 i, j, temp;

	memset(pdata->edid_buf, 0, EDID_SEG_SIZE);

	ret |= lt9611_write_reg(pdata, 0xff, 0x85);
	ret |= lt9611_write_reg(pdata, 0x03, 0xc9);
	ret |= lt9611_write_reg(pdata, 0x04, 0xa0);/* 0xA0 is EDID device address */
	ret |= lt9611_write_reg(pdata, 0x05, 0x00);/* 0x00 is EDID offset address */
	ret |= lt9611_write_reg(pdata, 0x06, 0x20);/* length for read */
	ret |= lt9611_write_reg(pdata, 0x14, 0x7f);

	for (i = 0; i < 8; i++) {
		ret |= lt9611_write_reg(pdata, 0x05, i * 32); /* offset address */
		ret |= lt9611_write_reg(pdata, 0x07, 0x36);
		ret |= lt9611_write_reg(pdata, 0x07, 0x31);
		ret |= lt9611_write_reg(pdata, 0x07, 0x37);
		mdelay(5);   /* wait 5ms before reading edid */

		ret |= lt9611_read_reg(pdata, 0x40, &temp, 1);
		printk(KERN_INFO "%s: temp = 0x%02x\n", __func__, temp);
		if (temp & 0x02) {                  /*KEY_DDC_ACCS_DONE=1*/

			if (temp & 0x50) {           /* DDC No Ack or Abitration lost */
				printk(KERN_INFO "%s: read edid failed: no ack\n",__func__);
				ret = -EINVAL;
				goto end;
			} else {
				for (j = 0; j < 32; j++)
					ret |= lt9611_read_reg(pdata, 0x83, &(pdata->edid_buf[i * 32 + j]), 1);
			}

		} else {
			printk(KERN_INFO "%s: read edid failed: access not done\n", __func__);
			ret = -EINVAL;
			goto end;
		}
	}

	printk(KERN_INFO "%s: read edid succeeded, checksum = 0x%x\n", __func__, pdata->edid_buf[255]);
end:
	ret |= lt9611_write_reg(pdata, 0x03, 0xc2);
	ret |= lt9611_write_reg(pdata, 0x07, 0x1f);
	return ret;
}



static irqreturn_t lt9611_interrupt_thread(int irq, void *dev_id)
{
    struct lt9611 *pdata = (struct lt9611*)dev_id;

    int ret = 0;

	u8 irq_flag0, irq_flag3;

	printk(KERN_INFO "%s: enter\n", __func__);
	ret |= lt9611_write_reg(pdata, 0xff, 0x82);
	ret |= lt9611_read_reg(pdata, 0x0f, &irq_flag3, 1);
	ret |= lt9611_read_reg(pdata, 0x0f, &irq_flag0, 1);  //0x0f -->0x0c

	/* hpd changed low */
	if (irq_flag3 & 0x80) {
		printk(KERN_INFO "%s:hdmi cable disconnected\n", __func__);

		ret |= lt9611_write_reg(pdata, 0xff, 0x82); /* irq 3 clear flag */
		ret |= lt9611_write_reg(pdata, 0x07, 0xbf);
		ret |= lt9611_write_reg(pdata, 0x07, 0x3f);
	}

	/* hpd changed high */
	if (irq_flag3 & 0x40) {
		printk(KERN_INFO "%s:hdmi cable connected\n", __func__);
		lt9611_read_edid(pdata);

		ret |= lt9611_write_reg(pdata, 0xff, 0x82); /* irq 3 clear flag */
		ret |= lt9611_write_reg(pdata, 0x07, 0x7f);
		ret |= lt9611_write_reg(pdata, 0x07, 0x3f);
	}

	/* video input changed */
	if (irq_flag0 & 0x01) {
		printk(KERN_INFO "%s:video input changed\n", __func__);
		ret |= lt9611_write_reg(pdata, 0xff, 0x82); /* irq 0 clear flag */
		ret |= lt9611_write_reg(pdata, 0x9e, 0xff);
		ret |= lt9611_write_reg(pdata, 0x9e, 0xf7);
		ret |= lt9611_write_reg(pdata, 0x04, 0xff);
		ret |= lt9611_write_reg(pdata, 0x04, 0xfe);
	}

	return IRQ_HANDLED;


}

static int lt9611_gpio_irq_init(struct lt9611 *pdata)
{
	int ret = 0;

	pdata->irq = gpio_to_irq(pdata->irq_gpio);
	ret = devm_request_threaded_irq(&pdata->i2c_client->dev, pdata->irq,
			NULL, lt9611_interrupt_thread,
			IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
			"lt9611_irq", pdata);
	if (ret) {
		printk(KERN_ERR "%s: requset irq failed\n", __func__);
	}
	else{
		printk(KERN_INFO "%s: succeeded!!\n", __func__);
	}

	return ret;
}

static int lt9611_irq_init_K(struct lt9611 *pdata)
{
	int ret = 0;

    ret |= lt9611_write_reg(pdata, 0xff, 0x82);
    ret |= lt9611_write_reg(pdata, 0x58, 0x0a);
    ret |= lt9611_write_reg(pdata, 0x59, 0x00);
    ret |= lt9611_write_reg(pdata, 0x9e, 0xf7);

    return ret;
}

static ssize_t lt9611_debug_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	if (!this_lt9611) {
		return -EINVAL;
	}

    printk("\n ================= [ Read Debug Show Start ] ================ \n");

    lt9611_read_video_debug(this_lt9611);
    lt9611_get_chip_id_debug(this_lt9611);
    lt9611_mipi_byte_clk_debug(this_lt9611);
    lt9611_pcr_mk_debug(this_lt9611);
    lt9611_htotal_stable_debug(this_lt9611);

    printk("\n ================= [ Read Debug Show End ] ================== \n");

	return 1;
}

static ssize_t lt9611_debug_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	if (!this_lt9611) {
		return count;
	}

    lt9611_config(this_lt9611);

	return count;
}


static DEVICE_ATTR(lt9611_debug, 0600, lt9611_debug_show, lt9611_debug_store);

/* LT9611 Chip initialization */
static int lt9611_config(struct lt9611 *pdata)
{
	int ret = 0;

	ret |= lt9611_reset(pdata);
	ret |= lt9611_read_device_rev(pdata);

    ret |= lt9611_init_system(pdata);

    ret |= lt9611_mipi_input_analog_K(pdata);
    ret |= lt9611_mipi_input_digital_K(pdata);
    ret |= lt9611_setup_pll(pdata);
    ret |= lt9611_setup_pcr(pdata);
    ret |= lt9611_setup_timing(pdata);

    ret |= lt9611_hdmi_tx_digital_K(pdata);
    ret |= lt9611_hdmi_tx_phy_K(pdata);
    ret |= lt9611_irq_init_K(pdata);
    ret |= lt9611_enable_hpd_interrupts(pdata);

    ret |= lt9611_enable_hdmi_out(pdata);

	if (ret) {
		printk(KERN_ERR "%s: failed to lt9611 init \n", __func__);
	}

	return ret;
}

static int lt9611_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct lt9611 *pdata;
	int ret = 0;

    printk("\n ====================[ LT9611 Probe Start ]=================== \n");

	printk(KERN_INFO "%s: i2c addr = 0x%02x enter!\n", __func__, client->addr);


	if (!client->dev.of_node) {
		printk(KERN_ERR "%s invalid argument\n", __func__);
		return -EINVAL;
	}

	pdata = devm_kzalloc(&client->dev, sizeof(struct lt9611), GFP_KERNEL);
	if (!pdata) {
		printk(KERN_ERR "%s: memory allocate failed.\n", __func__);
		return -ENOMEM;
	}

	pdata->i2c_client = client;
	this_lt9611 = pdata;
	i2c_set_clientdata(client, pdata);

	ret |= lt9611_parse_dt(&client->dev, pdata);
	if (ret) {
		printk(KERN_ERR "%s: failed to parse device tree\n", __func__);
		goto err_dt_parse;
	}

	ret |= lt9611_gpio_init(pdata);
	if (ret) {
		printk(KERN_ERR "%s: failed to configure GPIOs\n", __func__);
		goto err_dt_parse;
	}

	ret |= lt9611_gpio_irq_init(pdata);
    if (ret) {
		printk(KERN_ERR "%s: failed to GPIO IRQ \n", __func__);
	 	goto err_irq;
	}

	/* lt9611 init */
	ret |= lt9611_config(pdata);
    if (ret) {
		printk(KERN_ERR "%s: failed \n", __func__);
	 	goto err_irq;
	}

    /* Set a delayed work queue */
    INIT_DELAYED_WORK(&pdata->on_work, lt9611_on_work_fn);

    if(ret){
        printk(KERN_ERR "%s: probe failed!!!\n", __func__);
    }

	ret = device_create_file(&client->dev, &dev_attr_lt9611_debug);
	if (ret) {
		printk(KERN_ERR "%s: create debug sysfs fail!\n", __func__);
		goto err_i2c_prog;
	}

    /* 

	Must be read after the mipi channel begins to output data Enable 
	delay working sequence wait for 3 seconds

	 */
    schedule_delayed_work(&pdata->on_work, msecs_to_jiffies(3000));

    printk(KERN_INFO "%s: probe succeeded !!!\n", __func__);

	printk("\n ====================[ LT9611 Probe End ]=================== \n");

    return ret;

err_irq:
	device_remove_file(&client->dev, &dev_attr_lt9611_debug);
err_i2c_prog:
	lt9611_gpio_deinit(pdata);
err_dt_parse:
	devm_kfree(&client->dev, pdata);
	this_lt9611 = NULL;

    printk(KERN_ERR "%s: probe ERROR !!!\n", __func__);

	printk("\n ====================[ LT9611 Probe End ]=================== \n");

	return ret;
}

static int lt9611_gpio_deinit(struct lt9611 *pdata)
{
	gpio_direction_output(pdata->reset_gpio, 1);

	gpio_free(pdata->reset_gpio);
	gpio_free(pdata->irq_gpio);

    printk(KERN_INFO "%s: GPIO Deinit successfully", __func__);

	return 0;
}

static int lt9611_remove(struct i2c_client *client)
{
	struct lt9611 *pdata = (struct lt9611 *)i2c_get_clientdata(client);

	this_lt9611 = NULL;
	device_remove_file(&client->dev, &dev_attr_lt9611_debug);
	lt9611_gpio_deinit(pdata);
	i2c_set_clientdata(client, NULL);

	printk(KERN_INFO "%s: remove lt9611 driver successfully", __func__);

	return 0;
}


static struct i2c_device_id lt9611_id[] = {
	{ "lt9611", 0 },
	{}
};

static const struct of_device_id lt9611_match_table[] = {
	{ .compatible = "lontium,lt9611" },
	{}
};

MODULE_DEVICE_TABLE(of, lt9611_match_table);

static struct i2c_driver lt9611_driver = {
	.driver			= {
		.name		= "lt9611",
		.owner		= THIS_MODULE,
		.of_match_table = lt9611_match_table,
	},
	.probe			= lt9611_probe,
	.remove			= lt9611_remove,
	.id_table		= lt9611_id,
};

module_i2c_driver(lt9611_driver);

MODULE_AUTHOR("www.lckfb.com , LCKFB-lipeng");
MODULE_DESCRIPTION("LCKFB LT9611 MIPI to HDMI Expansion Board Driver");
MODULE_LICENSE("GPL");
